use std::cell::RefCell;
use std::collections::HashMap;
use std::str::FromStr;
use crate::address::{Wallet, Wallets};
use crate::blockchain::Blockchain;
use crate::proof_of_work::ProofOfWork;
use crate::transaction::Transaction;
use crate::util::{base58decode, base58encode, bytes_to_hex_string, hash_pub_key};
use crate::utxo_set::UTXOSet;

pub(crate) struct CLI {
}

impl CLI {
    pub fn new()->CLI {
        CLI {}
    }

    pub fn run(&mut self) {
        let mut args = std::env::args();
        if args.len() < 2 {
            println!("type --help to see help");
            println!();
            return;
        }

        args.next();

        match args.next().unwrap().as_str() {
            "printchain" => {
                self.print_chain();
            }
            "createblockchain" => {
                self.create_blockchain(&base58decode(&args.next().unwrap()));
            }
            "getbalance" => {
                self.get_balance(&base58decode(&args.next().unwrap()));
            }
            "send" => {
                let arg1=base58decode(&args.next().unwrap());
                let arg2=base58decode(&args.next().unwrap());
                let arg3=i32::from_str(args.next().unwrap().as_str()).unwrap();
                self.send(&arg1, &arg2, arg3);
            }
            "createwallet" => {
                self.create_wallet();
            }
            _ => {
                println!("No this command!");
                println!();
            }
        }
    }

    fn print_chain(&self) {
        let bc = Blockchain::new_blockchain(&"".as_bytes().to_vec());
        let mut bci = bc.iterator();

        loop {
            let block = bci.next();

            println!("Prev. hash: {}", bytes_to_hex_string(&block.prev_block_hash));
            // println!("Data: {}", String::from_utf8(block.data.to_vec()).unwrap());
            println!("Hash: {}", bytes_to_hex_string(&block.hash));
            let pbh_len = block.prev_block_hash.len();
            let pow = ProofOfWork::new_proof_of_work(RefCell::new(block));
            println!("PoW: {}\n", pow.validate());
            println!();

            if pbh_len == 0 {
                break;
            }
        }
    }

    fn create_blockchain(&mut self, address: &Vec<u8>) {
        let bc = Blockchain::new_blockchain(address);
        let mut utxo_set = UTXOSet {
            blockchain: bc,
            data: HashMap::new(),
        };
        utxo_set.reindedx();
        utxo_set.persist();
        println!("Success");
    }

    fn get_balance(&self, address: &Vec<u8>) {
        let pub_key_hash=address[1..address.len()-4].to_vec();
        let bc = Blockchain::new_blockchain(&address);
        let mut utxo_set = UTXOSet {
            blockchain: bc,
            data: HashMap::new(),
        };
        utxo_set.load_from_file();

        let mut balance = 0;
        let utos = utxo_set.find_utxo(&pub_key_hash);

        for out in &utos {
            balance += out.value
        }

        println!("Balance of {}: {}", base58encode(address), balance);
    }

    fn send(&self,from: &Vec<u8>, to: &Vec<u8>, amount: i32) {
        let mut bc = Blockchain::new_blockchain(from);
        let mut us = UTXOSet {
            blockchain: bc,
            data: HashMap::new(),
        };
        us.load_from_file();

        let mut bc = Blockchain::new_blockchain(from);
        let tx=Transaction::new_utxo_transaction(from, to, amount, &bc, &us);
        let cb_tx = Transaction::new_coinbase_tx(from, "");
        let nb=bc.mine_block(vec![cb_tx, tx]);

        us.update(&*nb.borrow());

        println!("Success");
    }

    fn create_wallet(&self) {
        let mut wallets = Wallets::new_wallets();
        let address = wallets.create_wallet();

        println!("Your new address: {}", address);
    }
}